package com.duojuhe.common.utils.file;

import com.duojuhe.common.exception.base.DuoJuHeException;
import com.duojuhe.common.result.ErrorCodes;
import com.duojuhe.common.utils.dateutils.DateUtils;
import com.duojuhe.common.utils.encryption.base64.Base64Util;
import com.duojuhe.common.utils.encryption.sm4.Sm4Util;
import com.duojuhe.common.utils.idgenerator.UUIDUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.servlet.http.HttpServletRequest;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;


/**
 * <br>
 * <b>功能：</b>文件工具类<br>
 */
@Slf4j
public class UploadFileUtil {
    //图片扩展名
    private final static String[] imageAllowFiles = {".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico"};
    //文件附件扩展名
    private final static String[] fileAttachmentAllowFiles = {
            ".rar", ".zip", ".tar", ".gz", ".7z", ".bz2", ".cab", ".iso",
            ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".txt", ".md", ".xml"
    };
    //视频扩展名
    private final static String[] videoAllowFiles = {
            ".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg",
            ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"};

    private static final String BACKSLASH_REGEX = "\\\\";
    private static final String SLASH = "/";
    private static final String SPOT = ".";
    private static final String SEMICOLON = ";";


    /**
     * 将图片转成base64格式
     *
     * @param file 上传的文件
     * @return base64字符串
     */
    public static String writeSingleImageToImageBase64(MultipartFile file) {
        try {
            //允许上传类型
            String[] allowFiles = {".png", ".jpg", ".jpeg", ".gif", ".bmp"};
            //允许的文件类型
            String allowFilesStr = ErrorCodes.UPLOAD_FILE_FORMAT_ERROR.getMessage() + ",允许文件类型:" + Arrays.toString(allowFiles);
            // 判断文件是否是指定类型
            if (!fileTypeIsConformed(file, allowFiles)) {
                throw new DuoJuHeException(allowFilesStr);
            }
            BufferedImage bufferedImage = getImageFile(file);
            if (bufferedImage == null) {
                throw new DuoJuHeException(allowFilesStr);
            }
            //宽度
            int width = bufferedImage.getWidth();
            //高度
            int height = bufferedImage.getHeight();
            if (width == 0 || height == 0) {
                throw new DuoJuHeException(allowFilesStr);
            }
            //图片类型
            String contentType = file.getContentType();
            if (StringUtils.isBlank(contentType)) {
                contentType = "image/jpeg";
            }
            return "data:" + contentType + ";base64," + Base64Util.encode(file.getBytes());
        } catch (IOException e) {
            throw new DuoJuHeException(ErrorCodes.UPLOAD_FILE_ERROR);
        }
    }

    /**
     * BufferedImage转成 base64
     *
     * @param bufferedImage
     * @param imageFormatName
     * @return
     * @throws IOException
     */
    public static String bufferedImageToBase64(BufferedImage bufferedImage, String imageFormatName) throws IOException {
        if (StringUtils.isBlank(imageFormatName)) {
            imageFormatName = "png";
        }
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, imageFormatName, stream);
        return "data:image/" + imageFormatName + ";base64," + Base64.getEncoder().encodeToString(stream.toByteArray());
    }

    /**
     * 图片文件文件写入磁盘
     *
     * @param file 上传的文件
     * @return 文件访问url
     */
    public static UploadFileBean writeSingleImageToDisk(MultipartFile file, String[] allowFiles, String filePrefixDomain, String rootPath, String urlPrefix) {
        try {
            //允许的文件类型
            String allowFilesStr = ErrorCodes.UPLOAD_FILE_FORMAT_ERROR.getMessage() + ",允许文件类型:" + Arrays.toString(allowFiles);
            // 判断文件是否是指定类型
            if (!fileTypeIsConformed(file, allowFiles)) {
                throw new DuoJuHeException(allowFilesStr);
            }
            BufferedImage bufferedImage = getImageFile(file);
            if (bufferedImage == null) {
                throw new DuoJuHeException(allowFilesStr);
            }
            //宽度
            int width = bufferedImage.getWidth();
            //高度
            int height = bufferedImage.getHeight();
            if (width == 0 || height == 0) {
                throw new DuoJuHeException(allowFilesStr);
            }
            // UUID文件名
            String uuidFileName = getFileName() + "_" + width + "x" + height;
            // 文件后缀
            String fileSuffix = getFileSuffix(file);
            String fileName = uuidFileName + fileSuffix;
            byte[] fileBytes;
            fileBytes = file.getBytes();
            // 相对路径
            String relativePath = getRelativePath(rootPath);
            // 文件存储路径
            String fullPath = rootPath + relativePath + fileName;
            //图片存在路径
            String fileStoragePath = writeSingleFileToDisk(fileBytes, fullPath);
            if (StringUtils.isBlank(fileStoragePath)) {
                throw new DuoJuHeException(allowFilesStr);
            }
            // 访问的url 转换url的分隔符
            String fileRelativePath = (urlPrefix + relativePath + fileName).replaceAll(BACKSLASH_REGEX, SLASH);
            //文件类型归类
            String fileTypeCode = getFileTypeCode(fileSuffix);
            //上传返回对象
            return buildUploadFileBean(file, filePrefixDomain, fileTypeCode, fileName, fileSuffix, fileStoragePath, fileRelativePath);
        } catch (IOException e) {
            throw new DuoJuHeException(ErrorCodes.UPLOAD_FILE_ERROR);
        }
    }


    /**
     * 文件文件写入磁盘
     *
     * @param file 上传的文件
     * @return 文件访问url
     */
    public static UploadFileBean writeSingleFileToDisk(MultipartFile file, String[] allowFiles, String filePrefixDomain, String rootPath, String urlPrefix) {
        try {
            //允许的文件类型
            String allowFilesStr = ErrorCodes.UPLOAD_FILE_FORMAT_ERROR.getMessage() + ",允许文件类型:" + Arrays.toString(allowFiles);
            // 判断文件是否是指定类型
            if (!fileTypeIsConformed(file, allowFiles)) {
                throw new DuoJuHeException(allowFilesStr);
            }
            // UUID文件名
            String uuidFileName = getFileName() + "_" + file.getSize();
            // 文件后缀
            String fileSuffix = getFileSuffix(file);
            String fileName = uuidFileName + fileSuffix;
            byte[] fileBytes;
            fileBytes = file.getBytes();
            // 相对路径
            String relativePath = getRelativePath(rootPath);
            // 文件存储路径
            String fullPath = rootPath + relativePath + fileName;
            //文件存储路径
            String fileStoragePath = writeSingleFileToDisk(fileBytes, fullPath);
            if (StringUtils.isBlank(fileStoragePath)) {
                throw new DuoJuHeException(allowFilesStr);
            }
            // 相对请求路径 转换url的分隔符
            String fileRelativePath = (urlPrefix + relativePath + fileName).replaceAll(BACKSLASH_REGEX, SLASH);
            //文件类型归类
            String fileTypeCode = getFileTypeCode(fileSuffix);
            //上传返回对象
            return buildUploadFileBean(file, filePrefixDomain, fileTypeCode, fileName, fileSuffix, fileStoragePath, fileRelativePath);
        } catch (IOException e) {
            throw new DuoJuHeException(ErrorCodes.UPLOAD_FILE_ERROR);
        }
    }

    /**
     * SM4加密 文件文件写入磁盘
     *
     * @param file 上传的文件
     * @return 文件访问url
     */
    public static UploadFileBean writeSm4SingleFileToDisk(MultipartFile file, String filePrefixDomain, String rootPath, String urlPrefix, String sm4Key) {
        try {
            if (StringUtils.isBlank(sm4Key)) {
                log.error("【Sm4加密文件上传】writeSm4SingleFileToDisk出现错误，Sm4加密秘钥为空!");
                throw new DuoJuHeException(ErrorCodes.FAIL);
            }
            // UUID文件名
            String uuidFileName = getFileName() + "_" + file.getSize();
            // 文件后缀
            String fileSuffix = getFileSuffix(file);
            String fileName = uuidFileName + fileSuffix;
            byte[] fileBytes;
            fileBytes = file.getBytes();
            // 相对路径
            String relativePath = getRelativePath(rootPath);
            // 文件存储路径
            String fullPath = rootPath + relativePath + fileName;
            //文件存储路径
            String fileStoragePath = writeSm4SingleFileToDisk(fileBytes, fullPath, sm4Key);
            // 相对请求路径 转换url的分隔符
            String fileRelativePath = (urlPrefix + relativePath + fileName).replaceAll(BACKSLASH_REGEX, SLASH);
            //文件类型归类
            String fileTypeCode = getFileTypeCode(fileSuffix);
            //上传返回对象
            return buildUploadFileBean(file, filePrefixDomain, fileTypeCode, fileName, fileSuffix, fileStoragePath, fileRelativePath);
        } catch (IOException e) {
            throw new DuoJuHeException(ErrorCodes.UPLOAD_FILE_ERROR);
        }
    }


    /**
     * 构建上传文件对象
     *
     * @param file
     * @param filePrefixDomain
     * @param newFileName
     * @param fileSuffix
     * @param fileStoragePath
     * @param fileRelativePath
     * @return
     */
    private static UploadFileBean buildUploadFileBean(MultipartFile file, String filePrefixDomain, String fileTypeCode, String newFileName, String fileSuffix, String fileStoragePath, String fileRelativePath) {
        if (StringUtils.isBlank(filePrefixDomain)) {
            //当前缀域名没有配置时，则自动获取
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            filePrefixDomain = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort();
        }
        //上传返回对象
        UploadFileBean bean = new UploadFileBean();
        bean.setFileSize(file.getSize());
        bean.setFileSuffix(fileSuffix);
        bean.setNewFileName(newFileName);
        bean.setOriginalName(file.getOriginalFilename());
        bean.setFileStoragePath(fileStoragePath);
        bean.setFileAbsolutePath(filePrefixDomain + fileRelativePath);
        bean.setFileRelativePath(fileRelativePath);
        bean.setFileTypeCode(fileTypeCode);
        return bean;
    }

    /**
     * 图片字节数组写入磁盘
     *
     * @param fileBytes 文件字节
     * @param fileName  文件名
     * @param rootPath  文件存放物理全路径
     * @param urlPrefix 文件访问前缀
     * @return 文件访问url
     */
    private static String writeSingleImageFileToDisk(byte[] fileBytes, String fileName, String rootPath, String urlPrefix) {
        // 相对路径
        String relativePath = getRelativePath(rootPath);
        // 文件存储路径
        String fullPath = rootPath + relativePath + fileName;
        BufferedOutputStream out = null;
        File targetFile = new File(fullPath);
        boolean noImageFlag = false;
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
            out = new BufferedOutputStream(fileOutputStream);
            out.write(fileBytes);
            out.flush();
            if (!isImageFile(targetFile)) {
                noImageFlag = true;
                return null;
            }
            // 访问的url
            String fileUrl = urlPrefix + relativePath + fileName;
            // 转换url的分隔符
            return fileUrl.replaceAll(BACKSLASH_REGEX, SLASH);
        } catch (IOException e) {
            return null;
        } finally {
            try {
                if (out != null) {
                    out.flush();
                    out.close();
                }
                if (noImageFlag) {
                    deleteFile(targetFile);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 删除文件或文件夹
     *
     * @param needDelFile 需要被删除的文件对象
     * @return 是否成功删除干净
     */
    public static boolean deleteFile(File needDelFile) {
        return needDelFile != null && needDelFile.exists() && deleteFile(true, needDelFile);
    }


    /**
     * 删除文件或文件夹
     *
     * @param lastFlg     上一次递归删除的结果
     * @param needDelFile 需要被删除的文件对象
     * @return 是否成功删除干净
     */
    private static boolean deleteFile(boolean lastFlg, File needDelFile) {
        //必须从里面先删除
        if (needDelFile.isDirectory()) {
            File[] files = needDelFile.listFiles();
            if (files == null) {
                return true;
            }
            for (File childFile : files) {
                lastFlg = lastFlg & deleteFile(lastFlg, childFile);
            }
        }
        boolean delResult = needDelFile.delete();
        return lastFlg && delResult;
    }

    /**
     * 文件后缀名
     *
     * @param file 原始文件名
     */
    public static String getFileSuffix(MultipartFile file) {
        if (file == null) {
            return "";
        }
        //文件名称
        String fileName = file.getOriginalFilename();
        String fileSuffix = FilenameUtils.getExtension(fileName);
        if (StringUtils.isBlank(fileSuffix)) {
            if (file.getContentType() != null) {
                fileSuffix = MimeTypeUtils.getExtension(file.getContentType());
            } else {
                fileSuffix = "";
            }
        }
        if (StringUtils.isBlank(fileSuffix)) {
            fileSuffix = getFileSuffixByFileName(fileName);
        }
        return SPOT + fileSuffix;
    }


    /**
     * 根据文件名取文件后缀
     *
     * @param fileName
     * @return
     */
    public static String getFileSuffixByFileName(String fileName) {
        try {
            if (StringUtils.isNotBlank(fileName)) {
                //对文文件的全名进行截取然后在后缀名进行删选。
                int begin = fileName.indexOf(SPOT);
                int last = fileName.length();
                //获得文件后缀名
                return fileName.substring(begin + 1, last);
            }
            throw new DuoJuHeException("文件名格式有误");
        } catch (Exception e) {
            throw new DuoJuHeException("文件名格式有误");
        }
    }


    /**
     * 根据文件后缀获取文件类型
     *
     * @param fileSuffix
     * @return
     */
    private static String getFileTypeCode(String fileSuffix) {
        if (StringUtils.isBlank(fileSuffix)) {
            return FileTypeEnum.FileTypeCode.OTHER_TYPE.getKey();
        }
        fileSuffix = fileSuffix.replace(SPOT, StringUtils.EMPTY).toLowerCase();
        if (Arrays.asList(imageAllowFiles).contains(fileSuffix)) {
            return FileTypeEnum.FileTypeCode.OTHER_TYPE.getKey();
        } else if (Arrays.asList(fileAttachmentAllowFiles).contains(fileSuffix)) {
            return FileTypeEnum.FileTypeCode.ATTACHMENT_TYPE.getKey();
        } else if (Arrays.asList(videoAllowFiles).contains(fileSuffix)) {
            return FileTypeEnum.FileTypeCode.VIDEO_TYPE.getKey();
        } else {
            return FileTypeEnum.FileTypeCode.OTHER_TYPE.getKey();
        }
    }

    /**
     * 字节数组写入磁盘
     *
     * @param fileBytes 文件字节
     * @param fullPath  文件存储路径
     * @return 文件存储路径
     */
    private static String writeSingleFileToDisk(byte[] fileBytes, String fullPath) {
        BufferedOutputStream out = null;
        File targetFile = new File(fullPath);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
            out = new BufferedOutputStream(fileOutputStream);
            out.write(fileBytes);
            out.flush();
            return fullPath;
        } catch (IOException e) {
            return null;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 国密4加密 字节数组写入磁盘
     *
     * @param fileBytes 文件字节
     * @param fullPath  文件存储路径
     * @return 文件存储路径
     */
    private static String writeSm4SingleFileToDisk(byte[] fileBytes, String fullPath, String sm4Key) {
        BufferedOutputStream out = null;
        File targetFile = new File(fullPath);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(targetFile);
            out = new BufferedOutputStream(fileOutputStream);
            out.write(Sm4Util.encryptFileCbc(sm4Key, fileBytes));
            out.flush();
            return fullPath;
        } catch (Exception e) {
            return null;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * 获取文件名称
     */
    private static String getFileName() {
        return UUIDUtils.getUUID32();
    }

    /**
     * 上传文件的根目录，不存在就创建
     */
    private static String getRelativePath(String rootPath) {
        // 图片上传的相对路径
        String relativePath = File.separator + DateUtils.dateToString(new Date(), DateUtils.DEFAULT_DATE_FORMAT) + File.separator;
        // 图片上传的完整路径
        String fullPath = rootPath + relativePath;
        File rootPathFile = new File(fullPath);
        if (!rootPathFile.exists() && !rootPathFile.isDirectory()) {
            if (!rootPathFile.mkdirs()) {
                return File.separator;
            }
        }
        return relativePath;
    }

    /**
     * 获取文件后缀
     *
     * @param base64Str base64字符串
     * @return 文件后缀
     */
    private static String getImageSuffixByBase64(String base64Str) {
        return base64Str.substring(base64Str.lastIndexOf(SLASH) + 1, base64Str.lastIndexOf(SEMICOLON));
    }

    /**
     * 判断是否是真图片
     *
     * @param multipartFile
     * @return
     */
    public static BufferedImage getImageFile(MultipartFile multipartFile) {
        try {
            return ImageIO.read(multipartFile.getInputStream());
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * 判断是否是真图片
     *
     * @param multipartFile
     * @return
     */
    public static boolean isImageFile(MultipartFile multipartFile) {
        try {
            BufferedImage bufferedImage = ImageIO.read(multipartFile.getInputStream());
            if (bufferedImage == null) {
                return false;
            }
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            return width != 0 && height != 0;
        } catch (IOException e) {
            return false;
        }
    }

    /**
     * 判断是否是真图片
     *
     * @param file
     * @return
     */
    public static boolean isImageFile(File file) {
        ImageInputStream iis = null;
        try {
            // resFile为需被
            iis = ImageIO.createImageInputStream(file);
            Iterator iter = ImageIO.getImageReaders(iis);
            if (!iter.hasNext()) {
                // 文件不是图片
                return false;
            }
            BufferedImage bi = ImageIO.read(file);
            return bi != null;
        } catch (IOException e) {
            return false;
        } finally {
            try {
                if (iis != null) {
                    iis.close();
                }
            } catch (IOException e) {

            }
        }
    }

    /**
     * @param fis        输入流对象
     * @param skiplength 跳过位置长度
     * @param length     要读取的长度
     * @return 字节数组
     * @throws IOException
     */
    private static byte[] readInputStreamAt(FileInputStream fis, long skiplength, int length) throws IOException {
        byte[] buf = new byte[length];
        fis.skip(skiplength);  //
        int read = fis.read(buf, 0, length);
        return buf;
    }

    /**
     * 检查文件类型
     */
    private static boolean fileTypeIsConformed(MultipartFile file, String[] allowFiles) {
        if (allowFiles == null) {
            return false;
        }
        String suffix = getFileSuffix(file);
        // 检验格式是否正确
        return Arrays.asList(allowFiles).contains(suffix);
    }

}
